﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using AzukiMap.DataSource;
using AzukiMap.Map;
using Geotools.Geometries;
using System.Collections.Generic;
using AzukiMap.Renderer;

namespace AzukiMap.Layer
{
    public abstract class TiledLayer : Layer
    {
        protected delegate int[] MoveTile(int left, int top);

        TileSource _tileData;
        int _baseUriNum;
        TileRenderer _renderer;
        GTPolygon _maxExtent;

        protected List<MoveTile> _moveTileMethods;
        protected List<string> _renderedTiles;

        public TiledLayer(TileSource tileData, MapCanvas mapCanvas, string name) 
            : base(mapCanvas, tileData.DataType)
        {
            GTPolygon maxExtent = tileData.MaxExtent;
            Init(tileData, name, maxExtent);
        }

        public TiledLayer(TileSource tileData, MapCanvas mapCanvas, string name, GTPolygon maxExtent) 
            : base(mapCanvas, tileData.DataType)
        {
            Init(tileData, name, maxExtent);
        }

        void Init(TileSource tileData, string name, GTPolygon maxExtent)
        {
            _renderer = new TileRenderer(_mapCanvas);
            _tileData = tileData;
            _baseUriNum = 0;
            _moveTileMethods = new List<MoveTile>();
            _renderedTiles = new List<string>();
            _maxExtent = maxExtent;
            SetTileMoveOrder();
        }

        public string GetBaseUri()
        {
            string baseUri = _tileData.BaseUris[_baseUriNum];
            _baseUriNum++;
            if (_baseUriNum == _tileData.BaseUris.Count)
            {
                _baseUriNum = 0;
            }
            return baseUri;
        }

        public override void Draw()
        {
            double tileScale = GetTileScale(_mapCanvas.GeoScale);
            GTPoint centerCoord = _mapCanvas.GeoCenter;
            //int tileLeft = GetTileLeftOrgX(centerCoord.X, tileScale);
            //int tileTop = GetTileTopOrgY(centerCoord.Y, tileScale);
            GTCoordinate tlPoint = GetTileOrigin(centerCoord.X, centerCoord.Y, tileScale);
            int tileLeft = (int)tlPoint.X;
            int tileTop = (int)tlPoint.Y;
            
            bool isAllTilesDrawed = false;
            bool needsMoreTile = false;
            int tilesOnSameDirection = 1;

            // Draw tiles in a spiral from center tile.
            while (!isAllTilesDrawed)
            {
                isAllTilesDrawed = true;
                foreach (MoveTile moveTile in _moveTileMethods)
                {
                    for (int k = 0; k < tilesOnSameDirection; k++)
                    {
                        GTPolygon tileExtent = GetTileExtent(tileScale, tileLeft, tileTop);

                        // Use tiles only intersects on a map canvas.
                        //if (_mapCanvas.CurrentExtent.Intersects(tileExtent))
                        if (IsOnMapCanvas(tileExtent))
                        {
                            string tileReqStr = CreateTileReqString(tileScale, tileLeft, tileTop);
                            if (!IsRendered(tileReqStr))
                            {
                                Tile tile = CreateTile(tileReqStr, tileExtent, tileScale);
                                _renderer.RenderTile(tile);
                                _renderedTiles.Add(tileReqStr);
                            }
                            isAllTilesDrawed = false;
                        }
                        int[] tilePosition = moveTile(tileLeft, tileTop);
                        tileLeft = tilePosition[0];
                        tileTop = tilePosition[1];
                    }
                    // Two directions use same tile number.
                    if (needsMoreTile)
                    {
                        tilesOnSameDirection++;
                        needsMoreTile = false;
                    }
                    else
                    {
                        needsMoreTile = true;
                    }
                    //tilesOnSameDirection++;
                }
            }
        }

        public override void Clear()
        {
            base.Clear();
            _renderedTiles.Clear();
        }

        void SetTileMoveOrder()
        {
            _moveTileMethods.Add(MoveTileUp);
            _moveTileMethods.Add(MoveTileRight);
            _moveTileMethods.Add(MoveTileDown);
            _moveTileMethods.Add(MoveTileLeft);
        }

        bool IsOnMapCanvas(GTPolygon extent)
        {
            bool isInCanvas = (_mapCanvas.CurrentExtent.Intersects(extent));
            bool isInMaxExtent = (_maxExtent.Contains(extent));
            return (isInCanvas && isInMaxExtent);
        }

        protected virtual Tile CreateTile(string tileReqStr, GTPolygon tileExtent, double tileScale)
        {
            string baseUri = GetBaseUri();
            Uri tileUri = new Uri(baseUri + tileReqStr);

            Tile tile = new Tile(tileUri,tileExtent, _tileData.TileWidth,
                _tileData.TileHeight,_tileData.Srid,_tileData.Dpi,tileScale,_tileData.MapUnit);
            return tile;
        }

        protected virtual GTPolygon GetTileExtent(double tileScale, int tlX, int tlY)
        {
            int blX = tlX;
            int blY = tlY + _tileData.TileHeight;
            int brX = blX + _tileData.TileWidth;
            int brY = blY;
            int trX = brX;
            int trY = tlY;

            GTCoordinate tl = GetGeoPointFromTileCoord(tlX, tlY, tileScale);
            GTCoordinate bl = GetGeoPointFromTileCoord(blX, blY, tileScale);
            GTCoordinate br = GetGeoPointFromTileCoord(brX, brY, tileScale);
            GTCoordinate tr = GetGeoPointFromTileCoord(trX, trY, tileScale);

            return _geoUtil.CreateExtentPolygon(tl.X, tl.Y, bl.X, bl.Y, br.X, br.Y, tr.X, tr.Y);
        }

        protected virtual double GetTileScale(double scale)
        {
            double nearestScale = 0;
            foreach (double tileScale in _tileData.Scales)
            {
                if (tileScale > scale)
                {
                    break;
                }
                nearestScale = tileScale;
            }

            if (nearestScale == 0)
            {
                nearestScale = _tileData.Scales[0];
            }
            return nearestScale;
        }

        protected bool IsRendered(string tileUri)
        {
            bool isRendered = false;
            if (_renderedTiles.Contains(tileUri))
            {
                isRendered = true;
            }
            return isRendered;
        }

        protected virtual int[] MoveTileUp(int left, int top)
        {
            top = top - _tileData.TileHeight;
            return new int[2]{left,top};
        }

        protected virtual int[] MoveTileRight(int left, int top)
        {
            left = left + _tileData.TileWidth;
            return new int[2]{left,top};
        }

        protected virtual int[] MoveTileDown(int left, int top)
        {
            top = top + _tileData.TileHeight;
            return new int[2]{left,top};
        }

        protected virtual int[] MoveTileLeft(int left, int top)
        {
            left = left - _tileData.TileWidth;
            return new int[2]{left,top};
        }

        protected abstract string CreateTileReqString(double tileScale, int tileLeft, int tileTop);
        protected abstract GTCoordinate GetGeoPointFromTileCoord(int tx, int ty, double tileScale);
        protected abstract GTCoordinate GetTileOrigin(double lon, double lat, double tileScale);

    }
}
